package pe.com.trestperu.common.controller;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import pe.com.trestperu.common.exception.CoreServiceException;
import pe.com.trestperu.common.listener.ContextLoaderListener;
import pe.com.trestperu.common.service.GenericService;
import pe.com.trestperu.common.util.PropertiesReader;

/**
 * Generic Maintenance Controller
 *
 * @author Telly Tataje Tirado
 *
 */
@SuppressWarnings("unchecked")
public class GenericMaintenanceController extends GenericController {

	protected PropertiesReader getPropertiesReader() {
		return (PropertiesReader)ContextLoaderListener.getBean(PropertiesReader.class);
	}

	/**
	 * Method to get GenericServiceImpl instance from Spring context
	 * 
	 * @return GenericService
	 */
	protected GenericService getGenericService() {
		return (GenericService) ContextLoaderListener.getBean(GenericService.class);
	}

	/**
	 * Method executed before clear every variable from modelAndView,
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void beforeInitBean(HttpServletRequest request, ModelAndView modelAndView) throws CoreServiceException {
	}

	/**
	 * Method to override GenericAction init method, execute this one 
	 * and set FLAG_BEAN_LIST, FLAG_LOAD, FLAG_VIEW in false
	 *  
	 * @param request
	 * @param response
	 * @return ModelAndView
	 * @throws Exception
	 */
	@RequestMapping("/{bean}/init")
	public ModelAndView init(@PathVariable("bean") String bean, HttpServletRequest request, ModelAndView modelAndView) throws Exception {
		beforeInitBean(request, modelAndView);
		ModelAndView _modelAndView = super.init(bean, request, modelAndView);
		_modelAndView.addObject(FLAG_BEAN_LIST, Boolean.FALSE);
		_modelAndView.addObject(FLAG_LOAD, Boolean.FALSE);
		_modelAndView.addObject(FLAG_VIEW, Boolean.FALSE);
		afterInitBean(request, _modelAndView);
		return _modelAndView;
	}

	/**
	 * Method will be executed after initialize every form variable
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void afterInitBean(HttpServletRequest request, ModelAndView form) throws CoreServiceException {
	}

	/**
	 * Method executed before to load bean in modelAndView
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void beforeLoadBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * This method perform load action using GenericService and
	 * Id configured in modelAndView get in getSelectedId method
	 * 
	 * @param modelAndView
	 * @param bean
	 * @return Object
	 * @throws Exception
	 */
	protected Object doLoad(ModelAndView modelAndView, Object bean) throws Exception {
		return getGenericService().findById(bean.getClass(), getSelectedId(modelAndView));
	}

	/**
	 * Method to load object in modelAndView
	 * 
	 * @param request
	 * @param modelAndView
	 * @return ModelAndView
	 * @throws Exception
	 */
	@RequestMapping("/load")
	public ModelAndView load(HttpServletRequest request, ModelAndView modelAndView) throws Exception {

		log.debug(" INI ACTION : cargar");
		try {
			beforeLoadBean(request, modelAndView);
			Object object = getSpringBean(getBeanClassName());
			Object bean = doLoad(modelAndView, object);
			modelAndView.addObject("bean", bean);
			afterLoadBean(request, modelAndView);
		} catch (Exception e) {
			log.error(e);
			modelAndView.addObject("message", getPropertiesReader().getMessage("general.load.error", e.getMessage()));
		}
		log.debug(" FIN ACTION : cargar");

		return displayController(request, modelAndView);
	}

	/**
	 * Method will be execute after load object in modelAndView
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void afterLoadBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method executed before find objects in database
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void beforeLookUpBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method used in lookUp method to get object list
	 * using getFindCriteria from GenericService
	 * 
	 * @param request
	 * @param modelAndView
	 * @return
	 * @throws CoreServiceException
	 */
	protected List<?> methodList(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
		return getGenericService().getFindCriteria(MapUtils.getObject(modelAndView.getModel(), "bean"));
	}

	/**
	 * Method to perform save action in database using save method
	 * from GenericService
	 * 
	 * @param form
	 * @param bean
	 * @throws CoreServiceException
	 */
	protected void doSave(ModelAndView modelAndView, Object bean) throws CoreServiceException {
		getGenericService().save(bean);
	}

	/**
	 * Method to search objects in database from object bean,
	 * filling beanList and change FLAG_BEAN_LIST to true
	 * if beanList if not empty
	 * 
	 * @param request
	 * @param modelAndView
	 * @return ModelAndView
	 * @throws Exception
	 */
	@RequestMapping("/lookUp")
	public ModelAndView lookUp(HttpServletRequest request,
			ModelAndView modelAndView) throws Exception {
		log.debug(" INI ACTION : lookUp");

		try {
			beforeLookUpBean(request, modelAndView);
			List<?> list = methodList(request, modelAndView);
			modelAndView.addObject("id", StringUtils.EMPTY);

			if (CollectionUtils.isNotEmpty(list)) {
				modelAndView.addObject(FLAG_BEAN_LIST, Boolean.TRUE);
				modelAndView.addObject("beanList", list);
			}else{
				modelAndView.addObject("beanList", ListUtils.EMPTY_LIST);
				modelAndView.addObject(FLAG_BEAN_LIST, Boolean.FALSE);
			}
			afterLookUp(request, modelAndView);
		} catch (Exception e) {
			log.error(e);
			modelAndView.addObject("message", getPropertiesReader().getMessage("general.lookUp.error", e.getMessage()));
		}
		log.debug(" END ACTION: lookUp");
		return displayController(request, modelAndView);
	}

	/**
	 * Method will be executed after lookUp
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void afterLookUp(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method executed before save object in database
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void beforeSaveBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method to perform save object in database
	 * 
	 * @param request
	 * @param modelAndView
	 * @return ModelAndView
	 * @throws Exception
	 */
	public ModelAndView save(HttpServletRequest request, ModelAndView modelAndView) throws Exception {

		log.debug(" INI ACTION : grabar");

		// reinicio mensajes
		message = "general.save.successful";

		try {
			beforeSaveBean(request, modelAndView);

			Object bean = MapUtils.getObject(modelAndView.getModel(), "bean");
			doSave(modelAndView, bean);
			modelAndView.addObject("message", getPropertiesReader().getMessage(message));

			afterSaveBean(request, modelAndView);

			modelAndView.addObject("id", StringUtils.EMPTY);

		} catch (Exception e) {
			log.error("No se pudo Grabar: " + e.getMessage());
			modelAndView.addObject("message", getPropertiesReader().getMessage("general.save.error", e.getMessage()));
			onErrorSaveBean(request, modelAndView);
		}

		log.debug(" FIN ACTION : grabar");
		return displayController(request, modelAndView);
	}

	/**
	 * Method to perform action on save error
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void onErrorSaveBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method will be executed after to perform save action
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void afterSaveBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method executed before delete action
	 * 
	 * @param request
	 * @param form
	 * @throws CoreServiceException
	 */
	protected void beforeDeleteBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method to execute delete from GenericService
	 * 
	 * @param modelAndView
	 * @param bean
	 * @throws CoreServiceException
	 */
	protected void doDelete(ModelAndView modelAndView, Object bean) throws CoreServiceException {
		getGenericService().delete(bean);
	}

	/**
	 * Method to perform delete from database
	 * 
	 * @param request
	 * @param modelAndView
	 * @return ModelAndView
	 * @throws Exception
	 */
	public ModelAndView delete(HttpServletRequest request, ModelAndView modelAndView) throws Exception {

		log.debug(" INI ACTION : eliminar");

		// reinicio mensajes
		message = "general.delete.successful";

		// Proceso de Eliminacion de elememento seleccionado
		try {
			beforeDeleteBean(request, modelAndView);
			if (ArrayUtils.isEmpty((String[])MapUtils.getObject(modelAndView.getModel(), "selectedId"))) {
				Object object = getSpringBean(getBeanClassName());
				String id = getGenericService().getIdFieldName(object.getClass());
				BeanUtils.setProperty(object, id, getSelectedId(modelAndView));

				doDelete(modelAndView,object);
				modelAndView.addObject("message", getPropertiesReader().getMessage(message));
			} else {
				String[] array = (String[])MapUtils.getObject(modelAndView.getModel(), "selectedId");
				for (int i = 0; i < array.length; i++) {
					String selectedId = array[i];
					Object object = getSpringBean(getBeanClassName());
					String id = getGenericService().getIdFieldName(object.getClass());
					BeanUtils.setProperty(object, id, selectedId);

					doDelete(modelAndView,object);
				}
				modelAndView.addObject("message", getPropertiesReader().getMessage(message));
			}
			afterDeleteBean(request, modelAndView);
			modelAndView.addObject("id", StringUtils.EMPTY);
			modelAndView.addObject("bean", getSpringBean(getBeanClassName()));
		} catch (Exception e) {
			log.error("No se pudo Eliminar: " + e.getMessage());
			modelAndView.addObject("message", getPropertiesReader().getMessage("general.delete.error", e.getMessage()));
		}

		log.debug(" END ACTION : eliminar");
		return displayController(request, modelAndView);
	}

	/**
	 * Method will be executed after to delete action
	 * 
	 * @param request
	 * @param modelAndView
	 * @throws CoreServiceException
	 */
	protected void afterDeleteBean(HttpServletRequest request, ModelAndView modelAndView)
			throws CoreServiceException {
	}

	/**
	 * Method to get id from ModelAndView to be used in load or delete method.
	 * For default, use id property from ModelAndView and try to convert in 
	 * String object. To any other case you need override this method
	 * 
	 * @param modelAndView
	 * @return Serializable
	 * @throws Exception
	 */
	public Serializable getSelectedId(ModelAndView modelAndView) throws Exception {
		if (StringUtils.isEmpty(MapUtils.getString(modelAndView.getModel(), "id"))) return null;
		return String.valueOf(MapUtils.getString(modelAndView.getModel(), "id"));
	}

	/**
	 * Method to verify if object id new before to save. This one return true if
	 * Object id configured in Hibernate is not null
	 * 
	 * @returns boolean
	 */
	public boolean isNew(Object gb) throws Exception {
		try {
			String id = ((GenericService) getSpringBean(GenericService.class)).getIdFieldName(gb.getClass());
			return PropertyUtils.getProperty(gb, id) == null;
		} catch (Exception e) {
			log.debug(e.getClass().getName() + ':' + e.getMessage());
			return false;
		}
	}

	/**
	 * Method to populate Map in ModelAndView with request parameters
	 * 
	 * @param modelAndView
	 * @param map
	 * @param request
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	public void populate(ModelAndView modelAndView, String map,
			HttpServletRequest request) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		boolean multipart = false;
		Object object = MapUtils.getObject(modelAndView.getModel(), map);
		if (object == null)
			object = MapUtils.EMPTY_MAP;
		if (ClassUtils.isAssignable(Map.class, object.getClass())) {
			Map<String, Object> orig = null;
			if(request.getContentType() != null)
			if (request.getContentType().startsWith("multipart/form-data")) {
				multipart = true;
				Enumeration<?> param = request.getParameterNames();
				while (param.hasMoreElements()) {
					if (orig == null)
						orig = new HashMap<String, Object>();
					String key = (String) param.nextElement();
					orig.put(key, request.getParameter(key));
				}
			} else {
				orig = request.getParameterMap();
			}

			Map<String, Object> dest = (Map<String, Object>) object;
			Iterator<String> it = dest.keySet().iterator();
			while (it.hasNext()) {
				String key = (String) it.next();
				if (multipart)
					dest.put(key, (MapUtils.getObject(orig, map.concat(".")
							.concat(key), null) != null ? orig.get(map.concat(
							".").concat(key)) : dest.get(key)));
				else
					dest.put(key, (MapUtils.getObject(orig, map.concat(".")
							.concat(key), null) != null ? ((String[]) orig
							.get(map.concat(".").concat(key)))[0] : dest
							.get(key)));
			}
		}
	}

    @InitBinder
    public void initBinder(WebDataBinder binder) {
    	SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
}